package cn.zhangfusheng.elasticsearch.factory.proxy;

import cn.zhangfusheng.elasticsearch.annotation.dsl.dynamic.Dynamic;
import cn.zhangfusheng.elasticsearch.annotation.dsl.mybatis.DslWithMybatis;
import cn.zhangfusheng.elasticsearch.annotation.dsl.sql.DslWithSql;
import cn.zhangfusheng.elasticsearch.mybatis.ElasticSearchWithMybatisXmlConfig;
import cn.zhangfusheng.elasticsearch.repository.ElasticSearchRepository;
import cn.zhangfusheng.elasticsearch.scan.ElasticSearchEntityRepositoryDetail;
import cn.zhangfusheng.elasticsearch.template.ElasticSearchRestTemplate;
import org.apache.commons.lang3.StringUtils;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * Repository 代理
 * @author fusheng.zhang
 * @date 2022-04-29 17:31:24
 */
public abstract class RepositoryProxy<T extends ElasticSearchRepository<?>> {

    private static final List<Method> DEFAULT_ELASTIC_SEARCH_REPOSITORY_METHODS = Arrays.asList(ElasticSearchRepository.class.getMethods());

    private final ElasticSearchEntityRepositoryDetail entityRepositoryDetail;
    private final ElasticSearchRestTemplate elasticSearchRestTemplate;
    private final ElasticSearchWithMybatisXmlConfig elasticSearchWithMybatisXmlConfig;
    private final String toString;

    public RepositoryProxy(
            ElasticSearchEntityRepositoryDetail entityRepositoryDetail,
            ElasticSearchRestTemplate elasticSearchRestTemplate,
            ElasticSearchWithMybatisXmlConfig elasticSearchWithMybatisXmlConfig) {
        this.entityRepositoryDetail = entityRepositoryDetail;
        this.elasticSearchRestTemplate = elasticSearchRestTemplate;
        this.elasticSearchWithMybatisXmlConfig = elasticSearchWithMybatisXmlConfig;
        this.toString = String.format("%s@%s",
                entityRepositoryDetail.getElasticSearchRepositoryClass().getName(),
                System.identityHashCode(this));
    }

    public abstract T getObject();

    /**
     * 解析 执行 返回值
     * @param method 方法
     * @param args   方法参数
     * @return
     */
    public Object getResult(Method method, Object[] args) {
        args = this.excludeEmpty(args);
        Map<String, Object> params = analysisMethodParams(args, method, entityRepositoryDetail.getMethodParamsIndex());
        String routing = entityRepositoryDetail.routing(params);
        String index = entityRepositoryDetail.getSearchIndex();
        Class<?> entityClass = entityRepositoryDetail.getEntityClass();
        // DslWithMybatis 注解解析
        DslWithMybatis dslWithMybatis = method.getAnnotation(DslWithMybatis.class);
        // DslWithSql 注解解析
        DslWithSql dslWithSql = method.getAnnotation(DslWithSql.class);
        // Dynamic 注解,动态字符串解析
        Dynamic dynamic = method.getAnnotation(Dynamic.class);
        // 执行解析
        if (Objects.nonNull(dslWithMybatis)) {
            return elasticSearchRestTemplate.runIbatis(
                    entityRepositoryDetail, method, args, params, routing, dslWithMybatis, elasticSearchWithMybatisXmlConfig, index);
        } else if (Objects.nonNull(dslWithSql)) {
            return elasticSearchRestTemplate.runWithSql(entityRepositoryDetail, method, args, routing, index);
        } else if (Objects.nonNull(dynamic)) {
            return elasticSearchRestTemplate.runDynamicStr(entityRepositoryDetail, method, args, routing, index);
        } else {
            return elasticSearchRestTemplate.runJpa(entityRepositoryDetail, method, args, routing, entityClass, index);
        }
    }

    /**
     * 默认方法验证
     * @param method
     * @return
     */
    @SuppressWarnings("BooleanMethodIsAlwaysInverted")
    public boolean isDefaultMethod(Method method) {
        return DEFAULT_ELASTIC_SEARCH_REPOSITORY_METHODS.contains(method);
    }

    /**
     * 排除空和null
     * @param args
     * @return
     */
    public Object[] excludeEmpty(Object[] args) {
        return Arrays.stream(args).map(arg -> {
            if (arg instanceof String) {
                if (StringUtils.isNotBlank(String.valueOf(arg))) return arg;
            } else if (arg instanceof Map) {
                if (!CollectionUtils.isEmpty((Map<?, ?>) arg)) return arg;
            } else if (arg instanceof Collection) {
                if (!CollectionUtils.isEmpty((Collection<?>) arg)) return arg;
            } else if (Objects.nonNull(arg)) {
                return arg;
            }
            return null;
        }).toArray();
    }

    /**
     * 封装参数列表
     * @param args
     * @param method
     * @return
     */
    public Map<String, Object> analysisMethodParams(
            Object[] args, Method method, Map<Method, Map<Integer, String>> methodParamsIndex) {
        Map<Integer, String> paramIndex = methodParamsIndex.get(method);
        HashMap<String, Object> result = new HashMap<>(paramIndex.size());
        paramIndex.forEach((index, paramName) -> result.put(paramName, args[index]));
        return result;
    }

    @Override
    public String toString() {
        return this.toString;
    }
}
